/* The digiModularInstaller
 * 
 * 
 * It does things to you.
 */
 
function digiModularInstaller(inputDirectory) {
	// properties
	this.moduleDirectory = inputDirectory; // grab the directory used at object creation
	this.moduleList; // container for our module names and paths
	this.loadedModules = new Array(); // container for modules that are loaded and need to be installed
	this.target = "."; // the default install target is the local machine but this can be changed
	
	// create new message collector
	var output = new messageCollector();
	
	// methods
	
	this.install = function() {
		this.findModules(); // read directory
		this.selectModules(); // load modules that need to be installed
		this.checkOS(); // check the os
		this.prioritizeModules(); // prioritize
		this.performInstall(); // do it to it
		return output.message; // return our log
	}
	
	// this searches inputDirectory for our installer classes
	this.findModules = function() {
		/* This function simply scans the given directory for
		 * files ending with ".app.js", trims out the class name
		 * (the part of the file name before ".app.js"), and returns
		 * this along with the file path.
		 */
		 
		 // first we will create our FSO to interact with the 
		 // file system on the local machine (where we will
		 // read the installer data)
		 var fso = new ActiveXObject("Scripting.FileSystemObject");
		 var objModuleDir = fso.GetFolder(this.moduleDirectory); // grab our module directory
		 var enumFiles = new Enumerator(objModuleDir.files); // contains list of all files
		 
		 var reAppExtension = new RegExp(".app.js","i"); // our search term
		 var rePathSeperator = new RegExp("\\\\","g"); // find the last seperator (we're looking for the last \)
		 
		 var appName = new Array();
		 var filePath = new Array();
		 var index = 0;
		 
		 while( !enumFiles.atEnd() ) {
		 	var tmpPath = "" + enumFiles.item(); // convert to string so we can do matching
			
			if( tmpPath.match(reAppExtension) ) {
				filePath[index] = tmpPath; // save the file path
				
				// extract the application name (I'm pretty sure this could be done with one)
				tmpPath.match(rePathSeperator);
				var fileName = RegExp.rightContext; // get the file name
				fileName.match(reAppExtension);
				appName[index] = RegExp.leftContext; // save the app name
				
				index++;
			}
			
		 	enumFiles.moveNext(); // go to the next file
		 }
		 
		 var modules = new Array(); // create info container
		 modules.call = appName;
		 modules.path = filePath;
		 
		 output.log("Found and loaded " + index + " application profiles.\n")
		 this.moduleList = modules; // return module list		 
	}
	
	// reads a list of class names and paths, evaluates the external js
	// and creates new objects of each classs. These are tested to see if we need
	// to install them. Modules requiring installation are placed in loadedModules
	this.selectModules = function() {
		var j = 0; // index for selected modules
		var i = 0; // index modules
		
		while( i < this.moduleList.call.length ) {
			eval( includeExtJS( this.moduleList.path[i] ) ); // load each class
			var callString = "new " + this.moduleList.call[i] + "(this.target)"; // create the class creation string
			var call = eval( callString ); // load up our classes!
			
			output.log( "Name:\t\t\t" + call.fullName )
			output.log( "Installed version:\t" + call.installedVersion );
			output.log( "Provided version:\t" + call.providedVersion );

			// new revision comparison using "complex" algorithm
			if( this.versionCompare(call.installedVersion, call.providedVersion) == 1 ) {
				output.log("\t\t\tMarked for Upgrade\n");
				this.loadedModules[j] = call;
				j++;
			} else {
				output.log("\n");
			}
			
			i++;
		}
		output.log( "Selected " + j + " modules to upgrade or install." );
	}
	// returns 1 if providedVersion is greater than installedVersion
	this.versionCompare = function(installedVersion, providedVersion){
		var reDelim = new RegExp("\\d+[,.]"); // delimiter regExp
		var rePerCom = new RegExp("[,.]"); // contains the delimiters
		var installedVersion = "" + installedVersion;
		var providedVersion = "" + providedVersion; // force strings
		
		if( !installedVersion | installedVersion == "0" ) {
			//WScript.Echo("not installed or version detection error")
			return 1;
		} else if( installedVersion.match(reDelim) ) {
			// delimiters exist, we must do complex comparison
			//WScript.Echo("using complex revision testing");
			// set up the matching on installedVersion
			var tmpInstRev = "" + installedVersion.match(reDelim); // set tmpInstRev to the first set
			var tmpInstRemainder = RegExp.rightContext; // get remaining revision numbers
			tmpInstRev = tmpInstRev.replace(rePerCom, ""); // remove comma or period
			
			// set up the matching on providedVersion
			var tmpProvRev = "" + providedVersion.match(reDelim); // set tmpProvRev to the first set
			var tmpProvRemainder = RegExp.rightContext; // get remainder
			tmpProvRev = tmpProvRev.replace(rePerCom, ""); // remove comma, period
			
			// perform major revision test
			if( parseInt(tmpInstRev,10) < parseInt(tmpProvRev,10) ) {
				//WScript.Echo("major revision catch");
				return 1;
			} else {
				// perform checking on subversions
				// loop until we're at the end.
				while( tmpInstRemainder != "" ) {		
					if( tmpInstRemainder.match(reDelim) ) {		
						tmpInstRev = "" + tmpInstRemainder.match(reDelim);
						tmpInstRemainder = RegExp.rightContext;
						tmpInstRev = tmpInstRev.replace(rePerCom, "");
						
						tmpProvRev = "" + tmpProvRemainder.match(reDelim);
						tmpProvRemainder = RegExp.rightContext;
						tmpProvRev = tmpProvRev.replace(rePerCom, "");
						
						// check minor revision number
						if( parseInt(tmpInstRev,10) < parseInt(tmpProvRev,10) ) {
							//WScript.Echo("minor revision catch");
							return 1;
						}
					} else {
						// no more delimiters, so we check the last set (build number)
						if( parseInt(tmpInstRemainder,10) < parseInt(tmpProvRemainder,10) ){
							//WScript.Echo("build revision catch");
							return 1;
						}
						tmpInstRemainder = ""; // clear tmpInstRemainder to kill the loop
					}
				} // end of loop
			}
			
			return 0;
		} else {
			// no delimiters, do simple compare
			//WScript.Echo("using simple revision testing");
			if( installedVersion < providedVersion ) {
				return 1;
			} else {
				return 0;
			}
		}
	}
	
	// prioritize the modules using the installPriority property, if provided
	this.prioritizeModules = function() {
		var prioritized = new Array();
		
		var i = 0; // inner loops counter
		var j = 0; // outer loop counter
		var offset = 0;
		
		var tmpLow;
		var tmpLowIndex;
		var totalLength = this.loadedModules.length;
		
		while( j < totalLength ) {
			while( i < this.loadedModules.length ) {
				// first, a fix for apps without a defined install priority.
				// without this, they will end up at the beginning of the list
				// (highest priority), which is no good!
				if( !this.loadedModules[i].installPriority ) {
					this.loadedModules[i].installPriority = 9999; // so we'll just give it one ;)
				}
				
				// for the first iteration, we just want to take the first value we see		
				if( i == 0 ) {
					tmpLow = this.loadedModules[i].installPriority;
					tmpLowIndex = i;
				} else if( tmpLow > this.loadedModules[i].installPriority ) {
					tmpLow = this.loadedModules[i].installPriority;
					tmpLowIndex = i;
				}
				i++;
			}
			
			// now that we have our lowest priority # (the first thing to do),
			// we want to make that the first item in the new array (or if this is
			// a subsequent iteration, the next item)
			prioritized[j] = this.loadedModules[tmpLowIndex];
			
			i = 0; // reset i so we can reuse it
			// rebuild modules without the object that has been prioritized
			var tmp = new Array(); // temporary array that must be recreated on each iteration
			
			while( i < this.loadedModules.length ) {
				if( i != tmpLowIndex ) {
					tmp[(i-offset)] = this.loadedModules[i];
				} else if( i == tmpLowIndex ) {
					offset = 1; // if we run into the prioritized value, we want to skip and set an offset
				}
				i++;
			}
			this.loadedModules = tmp; // reassign modules to shortened array
			i = 0; // reset i
			offset = 0; //reset offset
	
			j++; // lets do it again...
		}
		
		// debug
		var t = 0;
		output.log("Prioritized:")
		while( t < prioritized.length ) {
			output.log("prioritized[" + t + "] = " + prioritized[t].installPriority + " .... " + prioritized[t].fullName);
			t++;
		}
		
		this.loadedModules = prioritized; // overwrite loaded modules with our reordered array
	}
	
	this.checkOS = function() {
		eval(includeExtJS("lib\\wmiSystemTools.js")); // this should probably be a variable
		system = systemToolsGetSettings(this.target);
		output.log("Running on: " + system.OS);
		
		i = 0;
		var installModules = new Array();
		var y = 0; // counter for install modules
		
		while(i < this.loadedModules.length){
			// check for blank OS or any
			if( !this.loadedModules[i].osRequirement || ( this.loadedModules[i].osRequirement == "" ) || (this.loadedModules[i].osRequirement == "any") ){
				output.log(this.loadedModules[i].fullName + ":\tno os reqirement");
				installModules[y] = this.loadedModules[i];
				y++; // increment installModules counter
			} else {
				output.log(this.loadedModules[i].fullName + ":\t" + this.loadedModules[i].osRequirement)
				
				// array check
				if( this.loadedModules[i].osRequirement.constructor.toString().indexOf("Array") == -1 ) {
					var reOSMatch = new RegExp(this.loadedModules[i].osRequirement, "i"); // non array regexp
					if( system.OS.match(reOSMatch) ) {
						output.log("\t\tWill install");
						installModules[y] = this.loadedModules[i];
						y++;
					}
				} else {
					for(j=0; j<this.loadedModules[i].osRequirement.length; j++){
						var reOSMatch = new RegExp(this.loadedModules[i].osRequirement[j], "i");
						if( system.OS.match(reOSMatch) ){
							output.log("\t\tWill Install for " + this.loadedModules[i].osRequirement[j]);
							installModules[y] = this.loadedModules[i];
							y++;
							break;
						} // end of if
					} // end of for loop		
				} // end of second if-else
			} // end of first if-else
			
			i++;
		}
		
		for(b=0; b<installModules.length;b++){
			output.log("installing " + installModules[b].fullName);
		}
		
		this.loadedModules = installModules; // replace loaded modules with those that can be installed
	}
	
	this.performInstall = function() {
		var i = 0; // index modules
		
		while( i < this.loadedModules.length ) { 
			
			output.log( "Name:\t\t\t" + this.loadedModules[i].fullName )
			output.log( "Installed version:\t" + this.loadedModules[i].installedVersion );
			output.log( "Provided version:\t" + this.loadedModules[i].providedVersion + "\n" );
			
			var j = 0;
			while( j < this.loadedModules[i].install.length ) {
				output.log( "Install Command:\t" + this.loadedModules[i].install[j] );
				j++;	
			}
			
			// now we need to execute our install command
			// we're going to do this using WMI.
			// we should probably sanitize the "install command"
			eval(includeExtJS("lib\\wmiSystemTools.js")); // we need to fix this
			
			j = 0; // reset counter
			while( j < this.loadedModules[i].install.length ) {
				systemToolsExecuteCmd(this.target, "" + this.loadedModules[i].install[j] ); // must force a string
				
				/*
				var shell = WScript.CreateObject("WScript.Shell");
				var tmp = "" + this.loadedModules[i].install[j];
				shell.Exec(tmp);
				*/
				
				j++;
			}
			
			i++;
		}
	}	
}

// auxillary methods
function installTools(target) {
	this.wmiGetVersion = function(appName) {
		var WMISoftware = GetObject("winmgmts:{impersonationLevel=impersonate}!\\\\" + target + "\\root\\CIMV2");
		var colSoftware = WMISoftware.ExecQuery("Select * From Win32_Product");
		var enumSoftware = new Enumerator(colSoftware);
		var reAppName = new RegExp(appName, "i");
		
		// lets loop the installed programs searching for our requested app
		while( !enumSoftware.atEnd() ) {
			if( enumSoftware.item().Name.match(reAppName) ) {
				return enumSoftware.item().Version;
			} 
			enumSoftware.moveNext();
		}
	}
	
	// filePath must be double escaped string ie \ ==> \\\\
	this.wmiGetFileVersion = function(filePath) {
		var WMIConnect = GetObject("winmgmts:{impersonationLevel=impersonate}!\\\\" + target + "\\root\\CIMV2");
		var colFiles = WMIConnect.ExecQuery("Select * From CIM_Datafile Where Name = '" + filePath + "'");
		var enumFiles = new Enumerator(colFiles); // even though you should only get one file
		// we need to do a little error catching
		// this will fail if the file doesn't exist
		// so we will return -1 (and that should trigger an install)
		try{
			return enumFiles.item().Version; // we're just gonna look at the first (only) result
		} catch(e) {
			return -1;
		}
	}
	
	eval(includeExtJS("lib\\wmiSystemTools.js"));
	this.systemInfo = new systemToolsGetSettings(target)
	eval(includeExtJS("lib\\wmiRegistryTools.js"));
}

function messageCollector() {
	// collect messages
	this.message = "";
	this.log = function(input){
		if( this.message == "" ) {
			this.message = input;
		} else {
			this.message = this.message + "\n" + input;
		}
	}
}

// our good old include function
function includeExtJS(fileName) {
	var fso = new ActiveXObject('Scripting.FileSystemObject'); // create the file system object
	if( !fso.FileExists(fileName) ) { return -1; } // check for file existance and fail if no file
	var file = fso.OpenTextFile(fileName); // open file
	var stream = file.ReadAll(); // read file
	file.Close(); // close file
	return stream; // send it back (you need to evaluate the return)
}